/**
 * This is my package.
 */
package ajk;

import java.awt.Color;
import robocode.BulletHitEvent;
import robocode.HitByBulletEvent;
import robocode.HitRobotEvent;
import robocode.HitWallEvent;
import robocode.Robot;
import robocode.ScannedRobotEvent;

/**
 * 
 * @author Anthony Kinsey <AnthonyJK@gmail.com>
 * @version 2010.0912
 * @since 1.6
 * 
 * WallSpin Robot combines the strategies of spinning and moving along the walls. When hit by a
 * bullet or collided with another robot it switches between spinning and moving along the walls.
 * 
 */
public class WallSpin extends Robot {

  double maxDistance; // max distance on the map
  int mode = 0; // 0 is walling, 1 is spinning
  double lastBulletPower; // keeps track of the power of the last bullet fired
  long lastHit = 0; // keeps track of last time we were hit by a bullet
  boolean firstTimeWall = true; // is this the first time we are walling?
  boolean firstTimeSpin = true; // is this the first iteration of spin?

  /**
   * Loops forever between Walling and Spinning.
   */
  public void run() {
    // Set colors
    setBodyColor(Color.black);
    setGunColor(Color.black);
    setRadarColor(Color.black);
    setBulletColor(Color.red);
    setScanColor(Color.red);

    maxDistance = Math.max(getBattleFieldWidth(), getBattleFieldHeight());

    while (true) {
      if (mode == 0) {
        doWalling();
      }
      else if (mode == 1) {
        doSpinning();
      }
    }
  }

  /**
   * Makes the robot move to the top wall and start moving along the walls.
   */
  public void doWalling() {

    if (!firstTimeWall) {
      moveToCenter();
    }

    out.println("Current Mode: Walling");

    // Face north
    turnLeft(getHeading());
    ahead(maxDistance);
    if (firstTimeWall) {
      turnGunRight(90);
    }
    while (mode == 0) {
      turnRight(90);
      ahead(maxDistance);
    }
  }

  /**
   * Makes the robot move forward spin in a 360 so we can find someone to fire at, then it moves
   * back and turns left so we can start moving in another direction.
   */
  public void doSpinning() {
    firstTimeWall = false;
    moveToCenter();

    out.println("Current Mode: Spinning");

    while (mode == 1) {
      if (!firstTimeSpin) {
        ahead(250);
        firstTimeSpin = false;
      }
      turnRight(360);
      back(250);
      turnLeft(90);
    }
    firstTimeSpin = true;

  }

  /**
   * Moves the robot to the center of the arena.
   */
  public void moveToCenter() {
    double centerX = getBattleFieldWidth() / 2;
    double centerY = getBattleFieldHeight() / 2;

    double robotX = getX();
    double robotY = getY();

    // determine quadrant robot is in
    int quadrant = -1;
    if ((robotX - centerX) < 0 && (robotY - centerY) > 0) {
      quadrant = 0; // Quadrant 1
    }
    else if ((robotX - centerX) > 0 && (robotY - centerY) > 0) {
      quadrant = 1; // Quadrant 2
    }
    else if ((robotX - centerX) < 0 && (robotY - centerY) > 0) {
      quadrant = 2; // Quadrant 3
    }
    else if ((robotX - centerX) > 0 && (robotY - centerY) < 0) {
      quadrant = 3; // Quadrant 4
    }
    else if (Math.abs(robotX - centerX) < 0.001 && (robotY - centerY) > 0) {
      quadrant = 4; // Special Case: Robot on upper Y axis
    }
    else if (Math.abs(robotX - centerX) < 0.001 && (robotY - centerY) < 0) {
      quadrant = 5; // Special Case: Robot on lower Y axis
    }
    else if (Math.abs(robotX - centerX) < 0.001 && robotX > 0) {
      quadrant = 6; // Special Case: Robot on right X axis
    }
    else if (Math.abs(robotX - centerX) < 0.001 && robotX < 0) {
      quadrant = 7; // Special Case: Robot on left X axis
    }
    else if (Math.abs(robotX - centerX) < 0.001 && Math.abs(robotY - centerY) < 0.001) {
      return; // Special Case: Robot already in center, do nothing
    }

    // make specific movement based off quadrant
    switch (quadrant) {
    case 0:
      turnLeft(getHeading() + 180); // Faces robot south
      // get angle by arctan(opp/adj)
      turnLeft(Math.toDegrees(Math.atan(Math.abs(robotX - centerX) / Math.abs(robotY - centerY))));
      ahead(Math.sqrt(Math.pow(Math.abs(centerX - robotX), 2)
          + Math.pow(Math.abs(centerY - robotY), 2)));
      break;
    case 1:
      turnLeft(getHeading() + 180); // Faces robot south
      turnRight(Math.toDegrees(Math.atan(Math.abs(robotX - centerX) / Math.abs(robotY - centerY))));
      ahead(Math.sqrt(Math.pow(Math.abs(centerX - robotX), 2)
          + Math.pow(Math.abs((centerY - robotY)), 2)));
      break;
    case 2:
      turnLeft(getHeading()); // Faces robot north
      turnRight(Math.toDegrees(Math.atan(Math.abs(robotX - centerX) / Math.abs(robotY - centerY))));
      ahead(Math.sqrt(Math.pow(Math.abs(centerX - robotX), 2)
          + Math.pow(Math.abs(centerY - robotY), 2)));
      break;
    case 3:
      turnLeft(getHeading()); // Faces robot north
      turnLeft(Math.toDegrees(Math.atan(Math.abs(robotX - centerX) / Math.abs(robotY - centerY))));
      ahead(Math.sqrt(Math.pow(Math.abs(centerX - robotX), 2)
          + Math.pow(Math.abs(centerY - robotY), 2)));
      break;
    case 4:
      turnLeft(getHeading() + 180);
      ahead(Math.abs(centerY - robotY));
      break;
    case 5:
      turnLeft(getHeading());
      ahead(Math.abs(centerY - robotY));
      break;
    case 6:
      turnLeft(getHeading() + 90);
      ahead(Math.abs(centerX - robotX));
      break;
    case 7:
      turnLeft(getHeading() - 90);
      ahead(Math.abs(centerX - robotX));
      break;
    default:
      break;
    }
  }

  /**
   * If were walling and hit a wall ignore it. If were spinning and hit a wall move away.
   * 
   * @param e event when robot hits a wall
   */
  public void onHitWall(HitWallEvent e) {
    // Robot is walling
    if (mode == 0) {
      return; // Do nothing if were walling
    }
    // Robot is spinning
    else if (mode == 1) {
      back(100);
    }
  }

  /**
   * Moves back if the enemy is in front of us, and forward if it is behind us.
   * 
   * Note: This is borrowed from the Walls robot by Mathew A. Nelson and Flemming N. Larsen
   * 
   * @param e the event where robot hits wall
   */
  public void onHitRobot(HitRobotEvent e) {
    // If he's in front of us, set back up a bit.
    if (e.getBearing() > -90 && e.getBearing() < 90) {
      back(100);
    } // else he's in back of us, so set ahead a bit.
    else {
      ahead(100);
    }
  }

  /**
   * If we are hit by a bullet we want to switch between modes: If we are currently walling switch
   * to spinning and if we are currently walling switch to spinning.
   * 
   * @param e event when robot its hit by a bullet
   */
  public void onHitByBullet(HitByBulletEvent e) {
    long currentTime = System.currentTimeMillis() / 1000; // keep track of time

    // Wait 6 seconds before switching modes, otherwise
    // if we get hit multiple times within a few seconds
    // we will be switching modes non-stop and not attacking
    if (mode == 0 && (currentTime - lastHit) > 6) {
      lastHit = System.currentTimeMillis() / 1000;
      mode = 1;
    }
    else if (mode == 1 && (currentTime - lastHit) > 6) {
      lastHit = System.currentTimeMillis() / 1000;
      mode = 0;
    }
  }

  /**
   * If we scan a robot fire a bullet.
   * 
   * @param e event when robot is detected
   */
  public void onScannedRobot(ScannedRobotEvent e) {
    Double enPos = e.getDistance();

    fire(firePower(maxDistance, enPos));

  }

  /**
   * Fire bullet proportional to distance.
   * 
   * @param maxDistance this is the max distance of the battlefield
   * @param enemyPosition the position of the enemy
   * @return returns the firepower of the bullet based on the enemy's distance
   */
  public double firePower(double maxDistance, double enemyPosition) {
    if ((maxDistance * 0.85) <= enemyPosition && maxDistance >= enemyPosition) {
      lastBulletPower = 0.1;
    }
    else if ((maxDistance * 0.70) <= enemyPosition && (maxDistance * 0.85) > enemyPosition) {
      lastBulletPower = 0.5;
    }
    else if ((maxDistance * 0.55) <= enemyPosition && (maxDistance * 0.70) > enemyPosition) {
      lastBulletPower = 1.0;
    }
    else if ((maxDistance * 0.40) <= enemyPosition && (maxDistance * 0.55) > enemyPosition) {
      lastBulletPower = 1.5;
    }
    else if ((maxDistance * 0.25) <= enemyPosition && (maxDistance * 0.40) > enemyPosition) {
      lastBulletPower = 2.0;
    }
    else if ((maxDistance * 0.10) <= enemyPosition && (maxDistance * 0.25) > enemyPosition) {
      lastBulletPower = 2.5;
    }
    else if (0 <= enemyPosition && (maxDistance * 0.10) > enemyPosition) {
      lastBulletPower = 3.0;
    }
    return lastBulletPower;
  }

  /**
   * If we hit an enemy at close range, fire another high powered bullet.
   * 
   * @param e event when hit enemy with bullet
   */
  public void onBulletHit(BulletHitEvent e) {
    if (lastBulletPower > 2) {
      fire(3);
      lastBulletPower = 0;
    }
  }

}
